package io.mft.redis.connection;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.hutool.core.util.ObjectUtil;
import io.mft.redis.sentinel.ShardedJedisSentinelPool;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisSentinelPool;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.Protocol;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.util.Pool;

/**
 * 描述:	 获取datasource.xml数据库配置信息
 * 版权:	 Copyright (c) 2005
 * 公司:	 思迪科技
 * 作者:	 易庆锋
 * 版本:	 1.0
 * 创建日期: 2006-10-4
 * 创建时间: 21:33:09
 */
public final class RedisConfigure
{
	
	//private static Map<String,String> dataSourceNameMap = new ConcurrentHashMap<String, String>();
	
	private static Map<String,Object> dataSourceMap = new ConcurrentHashMap<String, Object>();
	
	private static Map<String,Map<String, String>> dbConnXmlMap = new ConcurrentHashMap<String, Map<String,String>>();
	
	private static String _default = "";
	
	private static Logger logger = LoggerFactory.getLogger(RedisConfigure.class);
	
	private static RedisConfigure instance=new RedisConfigure();
	
	
	
	private static JedisClusterCheckThread jedisClusterCheckThread;
	
	private RedisConfigure()
	{

	}
	
	public static void setDefault(String datasourceId) {
		_default = datasourceId;
	}
	public static String getDefault() {
		return _default;
	}
	/**
	 * 描述：获取实例
	 * 作者：易庆锋
	 * @return
	 */
	public static RedisConfigure getInstance()
	{
		return instance;
	}
	
	
	
	/**
	 * 描述：销毁数据源
	 */
	public static void destroyDataSource()
	{
		try
		{
			for (Iterator<String> iter = dataSourceMap.keySet().iterator(); iter.hasNext();)
			{
				String key = (String) iter.next();
				Pool<?> dataSource = (Pool<?>) dataSourceMap.get(key);
				dataSource.destroy();
			}
		}
		catch (Exception ex)
		{
		}
	}
	
	/**
	 * 
	 * 描述：格式化地址
	 * 作者：刘宝
	 * 时间：2019-7-30 下午8:00:30
	 * @param address
	 * @return
	 */
	private static List<String> formatAddress(String address)
	{
		List<String> temp = new ArrayList<String>();
		int index = address.indexOf("]");
		//判断是否ipv6的地址
		if(index > 1 && address.startsWith("[") && address.length() > index + 2)
		{
			String ip = address.substring(1,index);
			temp.add(ip);
			String last = address.substring(index + 2, address.length());
			temp.addAll(Arrays.asList(last.split(":")));
		}
		else
		{
			temp.addAll(Arrays.asList(address.split(":")));
		}
		return temp;
	}
	
	public static boolean addDatasourceConfig(String datasourceId,Map<String, String> config) {
		boolean s=false;
		if (!dataSourceMap.containsKey(datasourceId)) {
			Object obj=buildDataSource(config, datasourceId);
			dataSourceMap.put(datasourceId, obj);
			dbConnXmlMap.put(datasourceId, config);
			//logger.info("注册redis数据源:{}",datasourceId);
			s=true;
			if (ObjectUtil.isEmpty(_default)) {
				_default=datasourceId;
			}
		}
		return s;
	}
	/**
	 * 创建Redis数据源
	 *
	 * @param propMap
	 * @return
	 */
	private static Object buildDataSource(Map<String, String> propMap ,String dataSourceId)
	{
		String url = (String) propMap.get("url");
		String password = (String) propMap.get("password");
		if(ObjectUtil.isEmpty(password))
		{
			password = null;
		}
		String index = (String) propMap.get("index");
		if (ObjectUtil.isEmpty(index))
		{
			index = Protocol.DEFAULT_DATABASE + "";
		}
		String maxTotal = (String) propMap.get("maxTotal");
		String minIdle = (String) propMap.get("minIdle");
		String maxIdle = (String) propMap.get("maxIdle");
		String maxWait = (String) propMap.get("maxWait");
		String timeout = (String) propMap.get("timeout");
		if (ObjectUtil.isEmpty(timeout))
		{
			timeout = Protocol.DEFAULT_TIMEOUT + "";
		}
		String testOnBorrow = (String) propMap.get("testOnBorrow");
		String testOnReturn = (String) propMap.get("testOnReturn");
		String testWhileIdle = (String) propMap.get("testWhileIdle");
		String numTestsPerEvictionRun = (String) propMap.get("numTestsPerEvictionRun");
		String timeBetweenEvictionRunsMillis = (String) propMap.get("timeBetweenEvictionRunsMillis");
		String blockWhenExhausted = (String) propMap.get("blockWhenExhausted");
		String mode = (String) propMap.get("mode");
		if (ObjectUtil.isEmpty(mode))
		{
			mode = "0";
		}
		Object pool = null;
		try
		{
			JedisPoolConfig config = new JedisPoolConfig();
			config.setMaxTotal(Integer.valueOf(maxTotal));
			config.setMaxIdle(Integer.valueOf(maxIdle));
			config.setMinIdle(Integer.valueOf(minIdle));
			config.setMaxWaitMillis(Long.valueOf(maxWait));
			config.setTestOnBorrow(Boolean.valueOf(testOnBorrow));
			config.setTestOnReturn(Boolean.valueOf(testOnReturn));
			config.setTestWhileIdle(Boolean.valueOf(testWhileIdle));
			config.setNumTestsPerEvictionRun(Integer.valueOf(numTestsPerEvictionRun));
			config.setTimeBetweenEvictionRunsMillis(Integer.valueOf(timeBetweenEvictionRunsMillis));
			config.setBlockWhenExhausted(Boolean.valueOf(blockWhenExhausted));
			//0:普通非集群模式
			if (mode.equals("0"))
			{
				List<String> temp = formatAddress(url);
				if (temp != null && temp.size() >= 2)
				{
					String host = temp.get(0);
					int port = Integer.valueOf(temp.get(1));
					pool = new JedisPool(config, host, port, Integer.valueOf(timeout), password, Integer.valueOf(index));
				}
			}
			//1:普通Sharded分片集群模式
			else if (mode.equals("1"))
			{
				List<JedisShardInfo> shards = new ArrayList<JedisShardInfo>();
				String[] urls = url.split("|");
				if (urls != null && urls.length > 0)
				{
					for (String u : urls)
					{
						List<String> temp = formatAddress(u);
						if (temp != null && temp.size() >= 2)
						{
							String host = temp.get(0);
							int port = Integer.valueOf(temp.get(1));
							if(host.indexOf(":") > 0)
							{
								host = "[" + host + "]";
							}
							String shardInfoURI = "http://" + host + ":" + port + "/" + Integer.valueOf(index);
							JedisShardInfo shareInfo = new JedisShardInfo(shardInfoURI);
							if (ObjectUtil.isNotEmpty(password))
							{
								shareInfo.setPassword(password);
							}
							shareInfo.setConnectionTimeout(Integer.valueOf(timeout));
							shareInfo.setSoTimeout(Integer.valueOf(timeout));
							shards.add(shareInfo);
						}
					}
					if(shards.size() > 0)
					{
						pool = new ShardedJedisPool(config, shards);
					}
				}
			}
			//2：普通Sentinel主备集群模式
			else if (mode.equals("2"))
			{
				String[] urls = url.split("|");
				if (urls != null && urls.length > 0)
				{
					Set<String> masters = new LinkedHashSet<String>();
					Set<String> sentinels = new LinkedHashSet<String>();
					for (String u : urls)
					{
						List<String> temp = formatAddress(u);
						if (temp != null && temp.size() >= 2)
						{
							String host = temp.get(0);
							int port = Integer.valueOf(temp.get(1));
							String master = "";
							if (temp.size() > 2)
							{
								master = temp.get(2);
							}
							masters.add(master);
							sentinels.add(host + ":" + port);
						}
					}
					if(masters.size() > 0 && sentinels.size() > 0)
					{
						String[] masterAry = new String[masters.size()];
						masters.toArray(masterAry);
						pool = new JedisSentinelPool(masterAry[0], sentinels, config, Integer.valueOf(timeout), password, Integer.valueOf(index));
					}
				}
			}
			//3：Sharded+Sentinel，分片主备集群模式
			else if (mode.equals("3"))
			{
				String[] urls = url.split("|");
				if (urls != null && urls.length > 0)
				{
					Set<String> masters = new LinkedHashSet<String>();
					Set<String> sentinels = new LinkedHashSet<String>();
					for (String u : urls)
					{
						List<String> temp = formatAddress(u);
						if (temp != null && temp.size() >= 2)
						{
							String host = temp.get(0);
							int port = Integer.valueOf(temp.get(1));
							String master = "";
							if (temp.size() > 2)
							{
								master = temp.get(2);
							}
							masters.add(master);
							sentinels.add("[" + host + "]:" + port);
						}
					}
					if(masters.size() > 0 && sentinels.size() > 0)
					{
						List<String> masterList = new ArrayList<String>();
						masterList.addAll(masters);
						pool = new ShardedJedisSentinelPool(masterList, sentinels, config, Integer.valueOf(timeout), password, Integer.valueOf(index));
					}
				}
			}
			//4：Cluster分片集群模式
			else if (mode.equals("4"))
			{
				String[] urls = url.split("|");
				if (urls != null && urls.length > 0)
				{
					Set<HostAndPort> nodes = new LinkedHashSet<HostAndPort>();
					for (String u : urls)
					{
						List<String> temp = formatAddress(u);
						if (temp != null && temp.size() >= 2)
						{
							String host = temp.get(0);
							int port = Integer.valueOf(temp.get(1));
							HostAndPort hostAndPort = new HostAndPort(host, port);
							nodes.add(hostAndPort);
						}
					}
					if(nodes.size() > 0)
					{
						//JedisCluster中默认分装好了连接池.
						pool = new JedisCluster(nodes, Integer.valueOf(timeout), Integer.valueOf(timeout), 3, password,config);
						if(jedisClusterCheckThread == null)
						{
							jedisClusterCheckThread = new JedisClusterCheckThread(nodes,dataSourceId);
							jedisClusterCheckThread.start();
						}
					}
				}
			}
		}
		catch (Exception ex)
		{
			logger.error("", ex);
		}
		return pool;
	}
	
	/**
	 * 
	 * 描述：重构JedisCluster
	 * 作者：刘宝
	 * 时间：2017-4-10 下午10:25:34
	 * @param dataSourceId
	 * @param nodes
	 */
	public void reBuildJedisCluster(String dataSourceId,Set<HostAndPort> nodes)
	{
		Map<String,String> propMap = this.getDbConnXmlMap(dataSourceId);
		if (propMap != null)
		{
			//String url = (String) propMap.get("url");
			String password = (String) propMap.get("password");
			if(ObjectUtil.isEmpty(password))
			{
				password = null;
			}
			String maxTotal = (String) propMap.get("maxTotal");
			String minIdle = (String) propMap.get("minIdle");
			String maxIdle = (String) propMap.get("maxIdle");
			String maxWait = (String) propMap.get("maxWait");
			String timeout = (String) propMap.get("timeout");
			if (ObjectUtil.isEmpty(timeout))
			{
				timeout = Protocol.DEFAULT_TIMEOUT + "";
			}
			String testOnBorrow = (String) propMap.get("testOnBorrow");
			String testOnReturn = (String) propMap.get("testOnReturn");
			String testWhileIdle = (String) propMap.get("testWhileIdle");
			String numTestsPerEvictionRun = (String) propMap.get("numTestsPerEvictionRun");
			String timeBetweenEvictionRunsMillis = (String) propMap.get("timeBetweenEvictionRunsMillis");
			String blockWhenExhausted = (String) propMap.get("blockWhenExhausted");
			Object pool = null;
			try
			{
				JedisPoolConfig config = new JedisPoolConfig();
				config.setMaxTotal(Integer.valueOf(maxTotal));
				config.setMaxIdle(Integer.valueOf(maxIdle));
				config.setMinIdle(Integer.valueOf(minIdle));
				config.setMaxWaitMillis(Long.valueOf(maxWait));
				config.setTestOnBorrow(Boolean.valueOf(testOnBorrow));
				config.setTestOnReturn(Boolean.valueOf(testOnReturn));
				config.setTestWhileIdle(Boolean.valueOf(testWhileIdle));
				config.setNumTestsPerEvictionRun(Integer.valueOf(numTestsPerEvictionRun));
				config.setTimeBetweenEvictionRunsMillis(Integer.valueOf(timeBetweenEvictionRunsMillis));
				config.setBlockWhenExhausted(Boolean.valueOf(blockWhenExhausted));
				//JedisCluster中默认分装好了连接池.
				if(nodes.size() > 0)
				{
					pool = new JedisCluster(nodes, Integer.valueOf(timeout), Integer.valueOf(timeout), 1, password,config);
					Set newPoolNodes = new LinkedHashSet();
					newPoolNodes.addAll(((JedisCluster)pool).getClusterNodes().keySet());
					Set currentPoolNodes = new LinkedHashSet();
					for (Iterator iterator = nodes.iterator(); iterator.hasNext();)
					{
						HostAndPort node = (HostAndPort) iterator.next();
						currentPoolNodes.add(node.toString());
					}
					if(currentPoolNodes.size() == newPoolNodes.size() && currentPoolNodes.containsAll(newPoolNodes))
					{
						JedisCluster oldPool = (JedisCluster)this.getDataSource(dataSourceId);
						dataSourceMap.put(dataSourceId, pool);
						//延迟关闭应该才好
						new JedisClusterCloseThread(oldPool).start();
					}
					else
					{
						((JedisCluster)pool).close();
						Thread.sleep(1000);
						reBuildJedisCluster(dataSourceId,nodes);
					}
				}
			}
			catch (Exception ex)
			{
				logger.error("", ex);
			}
		}
	}
	
	/**
	 * 获得缺省数据源
	 *
	 * @return
	 */
	public Object getDataSource()
	{
		//若只有一个数据源，则直接返回相应的数据源
		if (dataSourceMap.size() == 1)
		{
			Object[] dataSourceArray = dataSourceMap.values().toArray();
			return dataSourceArray[0];
		}
		//若有多个数据源，则查找缺省数据源
		if (ObjectUtil.isEmpty(_default)) {
			Object[] dataSourceArray = dataSourceMap.values().toArray();
			return dataSourceArray[0];
		}else {
			return getDataSource(_default);
		}
	}
	
	/**
	 * 根据redis_datasource.xml文件中配置的数据源ID，得到对应的数据源对象
	 *
	 * @param id
	 * @return
	 */
	public Object getDataSource(String id)
	{
		if (ObjectUtil.isEmpty(id))
		{
			id = _default;
		}
		/**
		String ref = (String)dataSourceNameMap.get(id);
		if(ObjectUtil.isEmpty(ref))
		{
			ref = id;
		}
		**/
		return dataSourceMap.get(id);
	}
	
	/**
	 * 描述：判断datasourceid是否存在
	 * 作者：李建
	 * 时间：Dec 8, 2011 11:20:47 AM
	 * @param id
	 * @return
	 */
	public boolean isExistDataSource(String id)
	{
		if (ObjectUtil.isEmpty(id))
		{
			id = _default;
		}
		return dataSourceMap.containsKey(id);
	}
	
	/**
	 * 根据datasource.xml文件中配置的数据源ID，得到对应的数据源的xml配置
	 *
	 * @param id
	 * @return
	 */
	public Map<String,String> getDbConnXmlMap(String id)
	{
		if (ObjectUtil.isEmpty(id))
		{
			id = _default;
		}
		if (ObjectUtil.isEmpty(id))
		{
			return null;
		}
		else
		{
			return dbConnXmlMap.get(id);
		}
	}
}

class JedisClusterCloseThread extends Thread
{
	private static Logger logger = LoggerFactory.getLogger(JedisClusterCloseThread.class);
	private JedisCluster jedisCluster;
	public JedisClusterCloseThread(JedisCluster jedisCluster)
	{
		this.jedisCluster = jedisCluster;
	}
	
	public void run()
	{
		try
		{
			Thread.sleep(6000);
			this.jedisCluster.close();
		}
		catch(Exception e)
		{
			logger.error("",e);
		}
	}
}

class JedisClusterCheckThread extends Thread
{
	private static Logger logger = LoggerFactory.getLogger(JedisClusterCheckThread.class);
	private Set<HostAndPort> nodes = new LinkedHashSet<HostAndPort>();
	private Set<HostAndPort> prevNodes = new LinkedHashSet<HostAndPort>();
	private String dataSourceId;
	
	public JedisClusterCheckThread(Set<HostAndPort> nodes,String dataSourceId)
	{
		this.nodes.addAll(nodes);
		this.prevNodes.addAll(nodes);
		this.dataSourceId = dataSourceId;
	}
	
	public void run()
	{
		try
		{
			while(true)
			{
				Thread.sleep(1000);
				Set<HostAndPort> currentNodes = new LinkedHashSet<HostAndPort>();
				for (Iterator<HostAndPort> iterator = this.nodes.iterator(); iterator.hasNext();)
				{
					HostAndPort node = (HostAndPort) iterator.next();
					if(connectServer(node.getHost(), node.getPort()))
					{
						currentNodes.add(node);
					}
				}
				if(currentNodes.size() == this.prevNodes.size() && currentNodes.containsAll(this.prevNodes))
				{
					continue;
				}
				if(currentNodes.size() > this.prevNodes.size() && currentNodes.containsAll(this.prevNodes))
				{
					this.prevNodes = currentNodes;
					RedisConfigure.getInstance().reBuildJedisCluster(dataSourceId, this.prevNodes);
				}
				else
				{
					this.prevNodes = currentNodes;
					Map<String, String> propMap = RedisConfigure.getInstance().getDbConnXmlMap(dataSourceId);
					if (propMap != null)
					{
						String clusterNodeTimeout = (String)propMap.get("clusterNodeTimeout");
						if(ObjectUtil.isEmpty(clusterNodeTimeout))
						{
							clusterNodeTimeout = "5000";
						}
						Thread.sleep(Long.valueOf(clusterNodeTimeout));
					}
					RedisConfigure.getInstance().reBuildJedisCluster(dataSourceId, this.prevNodes);
				}
			}
		}
		catch(Exception e)
		{
			logger.error("",e);
		}
	}
	
	/**
	 *
	 * @param server   服务器IP地址
	 * @param port     端口
	 * @return
	 */
	public static boolean connectServer(String server, int port)
	{
		SocketChannel channel = null;
		boolean isAlive = false;
		try
		{
			channel = SocketChannel.open();
			SocketAddress address = new InetSocketAddress(server, port);
			channel.configureBlocking(true);
			channel.socket().setSoTimeout(10);
			isAlive = channel.connect(address);
		}
		catch (Exception ex)
		{
			logger.info(ex.getMessage());
			return false;
		}
		finally
		{
			if (channel != null)
			{
				try
				{
					channel.close();
				}
				catch (IOException e)
				{
					logger.info(e.getMessage());
				}
			}
		}
		return isAlive;
	}
	
}